package controllers;

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Observer;
import javax.imageio.ImageIO;
import javax.swing.Timer;
import model.ImageLayers;
import model.SearchPhotoModel;
import model.SearchResultModel;
import model.UpdatePhotoLayersModel;
import model.UserGroup;
import model.UserPermissions;
import model.ViewPhotoModel;
import view.UpdatePhotoLayersGUI;
import view.ViewPhotoGUI;
import view.ViewPhotoSampleGUI;
import client.CliMessage;
import client.ImageSerializer;


/**
 * The Class ViewPhotoController.
 * @author Dmitry
 */
public class ViewPhotoController extends AbstractController implements IValidateInput {

	/** The view photo view. */
	private ViewPhotoGUI viewPhotoView;
	
	/** The view photo model. */
	private ViewPhotoModel viewPhotoModel;
	
	ArrayList<UserGroup> currentUserLayerGroups;
	
	/** This flag equals TRUE if we deleted a photo from DB, else equals FALSE*/
	private boolean deleteFlag = false;
	private boolean checkExperationDate = false;
	private boolean foundExpired = false;
	private UserPermissions permission  = UserPermissions.getInstance(0, null, null);
	/////////////////////////////////////////////////////////////
	private ArrayList<BufferedImage> arr = new ArrayList<BufferedImage>();
	int indexToAnimate = 0;
	Timer timer = new Timer(4000, new PlayAnimation());
	MergePhoto merge = new MergePhoto();
	ArrayList<ImageLayers> imagesToDisplay  = new ArrayList<ImageLayers>();
	private java.util.Date date;
	private Timestamp timestamp;
	
	/**
	 * Instantiates a new view photo controller.
	 *
	 * @param theView view photo view.
	 * @param theModel view photo model.
	 */
	public ViewPhotoController(ViewPhotoGUI theView, ViewPhotoModel theModel) {
		super(theView, theModel);
		viewPhotoView = theView;
		viewPhotoModel = theModel;
		viewPhotoView.addPlusButtonListener(new PlusListener());
		viewPhotoView.addMinusButtonListener(new MinusListener());
		viewPhotoView.addResetButtonActionListener(new ResetListener());
		viewPhotoView.addDeleteButtonListener(new DeleteListener());
		viewPhotoView.addPlayButtonActionListener(new PlayListener());
		viewPhotoView.addStopButtonActionListener(new StopListener());
		viewPhotoView.addNextButtonActionListener(new NextListener());
		viewPhotoView.addPrevButtonActionListener(new PrevListener());
		viewPhotoView.addUpdateLayerButtonListener(new UpdateLayerListener(this));
		viewPhotoView.addDropLayersButtonListener(new DropLayersListener());
		
		
		// TODO - Check user's permissions.
		if(permission.userHasGroup("Daily Photo"))
			viewPhotoView.setAnimationButtons();
		if(permission.userHasGroup("Archive Worker"))
			viewPhotoView.addArchiveButtonsToWindow();
		if(permission.userHasGroup("Business Layer") || permission.userHasGroup("Infrastructure Layer"))
			checkExparationDate();
		createSearchPhotosQuery();
		
	}

	/**
	 * This method sends search query to the DataBase.
	 */
	public void searchDB(String sql)
	{	
		sendToServer(sql);
	}
	
	/**
	 * This method creates a query containing requested both photo's and layer's details
	 */
	private void createSearchPhotosQuery(){
		//get all photo details to display and prepare suitable queries
		SearchResultModel allPhotos =  viewPhotoModel.getPhotosDetails();
		ArrayList<SearchPhotoModel> photoList =  allPhotos.getPhotoHeadlineList();
				
		//get all the layers the user is subscribed to
		UserPermissions up = UserPermissions.getInstance(0, null,null);
		ArrayList<UserGroup> currentUserGroups = up.getUserGroups();
		currentUserLayerGroups = new ArrayList<UserGroup>();
		for (int i=0;i<currentUserGroups.size();i++) 
		{
			UserGroup curr = currentUserGroups.get(i);
			if(curr.getGroupName().contains("Layer"))
				currentUserLayerGroups.add(curr);
		} 
				 
				
		//Concat a query list of the form: PQ<LQ<LQ<LQ>PQ<LQ<LQ
		String sql = "getphoto";
				
		//get all requested photo data from the model and create the appropriate string of queries to send 
		for (int i = 0; i < photoList.size(); i++) 
		{
			//get the photo timestamp and location from the model
			 SearchPhotoModel photoDetails =  photoList.get(i);
			 String timestamp = photoDetails.getTimeStamp();
			 int location = photoDetails.getLocation();
					
			 //if this is not the first photo, add ">" before it
			 if(i>0)
				 sql+=">";
					 
			//int layer = i+1;
			sql+=
			getPhotoQuery( timestamp, location);
					
			//get the all the layers the user is subscribed to (if the photo has them) 
					
			for (int j = 0; j < currentUserLayerGroups.size(); j++) 
			{
				String layerName = currentUserLayerGroups.get(j).getGroupName();
				sql+="<"+getLayerQuery(timestamp,location,layerName);
			}
		}
		searchDB(sql);
	}
	
	/**
	 * This method creates a query for getting requested photos we need from the DB
	 * @param timestamp
	 * @param location
	 * @return
	 */
	private String getPhotoQuery(String timestamp,int location)
	{
		String photoQuery =
		"SELECT p.linktofile " 
				+" FROM photos AS p " 
				+" WHERE p.timestamp = '"+timestamp+"'"
				+" AND p.location = "+location;
		return photoQuery;
	}
	
	/**
	 * This method creates a query for getting requested layers from the DB
	 * @param timestamp - Photo's TimeStamp 
	 * @param location - Photo's Location 
	 * @param layerName - Photo's layer name
	 * @return
	 */
	private String getLayerQuery(String timestamp,int location,String layerName)
	{
		String layerQuery =
		"SELECT pl.linktofile "
		+"FROM photos AS p , photolayer AS pl, layertypes AS lt "
		+"WHERE pl.TimeStamp = p.TimeStamp "
		+"AND pl.Location = p.Location "
		+"AND pl.LayerType = lt.LayerType "
		+"AND p.TimeStamp = '"+timestamp+"' "
		+"AND p.Location = "+location+" "
		+"AND lt.LayerName = '"+layerName+"'";
		return layerQuery;
	}
	
	private void checkExparationDate(){
		// Gets Current TimeStamp
		date = new java.util.Date(System.currentTimeMillis());
		timestamp = new Timestamp(date.getTime());
		checkExperationDate = true;
		String sql = "searchSELECT cus1.accountID,cus1.Active,g.gid,g.gname "
							+"FROM sgi.customer_account AS cus1, sgi.groups AS g "
							+"WHERE cus1.TimeStamp < '" + timestamp + "' "
							+"AND cus1.subscription_type = g.gid "
							+"AND cus1.Active = 1 "
							+"AND g.gname = 'Business Layer'"
							+"UNION "
							+"SELECT cus1.accountID,cus1.Active,g.gid,g.gname "
							+"FROM sgi.customer_account AS cus1, sgi.groups AS g "
							+"WHERE cus1.TimeStamp < '" + timestamp + "' "
							+"AND cus1.subscription_type = g.gid "
							+"AND cus1.Active = 1 "
							+"AND g.gname = 'Infrastructure Layer'";
		searchDB(sql);	
	}
	
	/**
	 * This method handles photo's display in the view.
	 */
	@Override
	public void handlePhoto(CliMessage msgWithTryImage){
		
		ArrayList<HashMap<String,String>> resultToDisplay = msgWithTryImage.getSrs();
		
		// Creates Future shown photo's instance and saves it as string type
		String serializedPhoto ;
				
		// Create ImageSerializer instance in order to decode photo's string into BufferedImage type
		ImageSerializer imageSerializer = new ImageSerializer(""); // null string - we don't save photos on hard drive
				
		// output - photo decoded from String into an array of bytes
		byte[] output ;
				
		// Convert byte's array into ByteArrayInputStream which contains all the bytes that may be read from the stream
		ByteArrayInputStream bais;
		        
        try {
        	// read the photo into a BufferedImage type
			for (int i = 0; i < resultToDisplay.size(); i++)
			{
				serializedPhoto = resultToDisplay.get(i).get("PHOTO");
				output = imageSerializer.decodeImage(serializedPhoto);
				bais = new ByteArrayInputStream(output);
				String timeStamp = viewPhotoModel.getPhotosDetails().getPhotoHeadlineList().get(i).getTimeStamp();
				int location = viewPhotoModel.getPhotosDetails().getPhotoHeadlineList().get(i).getLocation();
				imagesToDisplay.add(new ImageLayers(timeStamp,location,ImageIO.read(bais))); 
				
				for (int j = 0; j < currentUserLayerGroups.size(); j++)
				{
					String layerName = currentUserLayerGroups.get(j).getGroupName();
					if(resultToDisplay.get(i).get(layerName).equals("**NOLAYER**")){
						imagesToDisplay.get(i).setLayer(layerName, null);
					}
					else 
					{
						serializedPhoto = resultToDisplay.get(i).get(layerName);
						output = imageSerializer.decodeImage(serializedPhoto);
						bais = new ByteArrayInputStream(output);
						viewPhotoView.setComboBoxContent(layerName);
						
						imagesToDisplay.get(i).setLayer(layerName, ImageIO.read(bais));
					}
				}
				
			}
			
			// Demonstration to TAL
			/*ViewPhotoSampleGUI theViewSample = new ViewPhotoSampleGUI();
			ViewPhotoSampleController cont = new ViewPhotoSampleController(theViewSample, viewPhotoModel.getPhotosDetails().getPhotoHeadlineList().get(0));
			
			setCurrentObserver(cont);
			setBackListener(this, theViewSample);*/
			
		//TODO - organize all this (animation/layers)
		//TODO - create a dynamic layer array
			
		viewPhotoView.addComboBoxListener(new ComboBoxListener());		
		showImageInWindow(imagesToDisplay.get(indexToAnimate).getImage());
		
			
		} catch (IOException e1) {
			
			e1.printStackTrace();
		}
}
	
	/**
	 * This class handles image animation.
	 * It creates a image slide show.
	 * @author Dmitry
	 *
	 */
	class PlayAnimation implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent arg0) {
			indexToAnimate = (indexToAnimate+1)% imagesToDisplay.size();
			// TODO - check if this row is necessary
//			showImageInWindow(imagesToDisplay.get(indexToAnimate).getImage());			
		}

	}
	
	private void showImageInWindow(BufferedImage image){
		viewPhotoView.setImage(resizedImage(image));
		viewPhotoView.displayImage();
		viewPhotoView.setLblShowLocation(imagesToDisplay.get(indexToAnimate).getLocation());
		viewPhotoView.setLblShowTimeStamp(imagesToDisplay.get(indexToAnimate).getTimeStamp());
		viewPhotoView.revalidate();
	    viewPhotoView.repaint();
	}
	
	/**
	 * The listener interface for receiving delete events.
	 * This class handles "Delete" button clicks.
	 * @see DeleteEvent
	 */
	class DeleteListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			if(viewPhotoView.showWarningMessage()){
				System.out.println("DELETED");
				deleteFlag = true;
				searchDB("adu" + "DELETE FROM photos " +
						 "WHERE linktofile = '" + viewPhotoModel.getPhotosDetails().getPhotoHeadlineList().get(indexToAnimate).getLinkToFile() + "'");
			}
			  
		}
		
	}
	
	/**
	 * This class handles "Play" button click.
	 * it plays the animations.
	 * @author Dmitry
	 *
	 */
	class PlayListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent arg0) {
			timer.start();
		}
		
	}
	
	/**
	 * This class handles "Stop" button clicks.
	 * it stops the animation.
	 * @author Dmitry
	 *
	 */
	class StopListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent arg0) {
			timer.stop();
		}
		
	}
	
	/**
	 * This class handles "Next" button clicks.
	 * it shows the next photo.
	 * @author Dmitry
	 *
	 */
	class NextListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent arg0) {
			timer.stop();
			indexToAnimate = (indexToAnimate+1)% imagesToDisplay.size();
			showImageInWindow(imagesToDisplay.get(indexToAnimate).getImage());
		}
	}
	
	/**
	 * This class handles "Previous" button clicks.
	 * it shows the previous photo.
	 * @author Dmitry
	 *
	 */
	class PrevListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent arg0) {
			timer.stop();
			indexToAnimate = (indexToAnimate-1)% imagesToDisplay.size();
			if(indexToAnimate < 0 )
				indexToAnimate = imagesToDisplay.size() + indexToAnimate;
			showImageInWindow(resizedImage(imagesToDisplay.get(indexToAnimate).getImage()));
		}
		
	}
	
	/**
	 * This class handles Combo box clicks.
	 * it shows available layers the user can put on a photo.
	 * @author Dmitry
	 *
	 */
	class ComboBoxListener implements ActionListener{
		
		@Override
		public void actionPerformed(ActionEvent arg0) {
			BufferedImage toShow = null;
			String chosenLayer = viewPhotoView.getComboBoxSelectedItem();
			System.out.println("The chosen file is: " + chosenLayer);
			toShow = merge.mergeLayer(imagesToDisplay.get(indexToAnimate).getNewImage(), imagesToDisplay.get(indexToAnimate).getLayer(chosenLayer));
			if(toShow == null)
				return;
			showImageInWindow(toShow);
		}
		
	}
	
	/**
	 * This class handles "Drop Layers" button click.
	 * it drops all the layers from the photo and shows the original image.
	 * @author Dmitry
	 *
	 */
	class DropLayersListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent arg0) {
			BufferedImage toShow;
			merge.resetPhoto();
			toShow = imagesToDisplay.get(indexToAnimate).getImage();
			showImageInWindow(toShow);
		}
		
	}
	
	/**
	 * This class handles "Reset" button clicks.
	 * @author Dmitry
	 *
	 */
	class ResetListener implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			viewPhotoView.resetPhotoSize();
		}
		
	}
	
	/**
	 * This class handles "Plus" button clicks.
	 * It zooms into the picture.
	 * @author Dmitry
	 *
	 */
	class PlusListener implements ActionListener{
		
		@Override
		public void actionPerformed(ActionEvent arg0) {
			timer.stop();
			viewPhotoView.zoomIn();
		}
	}
	
	/**
	 * This class handles "Minus" button clicks.
	 * It zooms out of the picture.
	 * @author Dmitry
	 *
	 */
	class MinusListener implements ActionListener{
		@Override
		public void actionPerformed(ActionEvent e) {
			timer.stop();
			viewPhotoView.zoomOut();
		}
	}
	
	/**
	 * This class handles "Update Photo Layers" button clicks.
	 * It opens an "Update Photo Layer" window.
	 * @author Dmitry & Tomer
	 *
	 */
	class UpdateLayerListener extends AbstractListener
	{

		/** The previous observer. */
		private Observer previousObserver;

		/**
		 * Instantiates a new update layer listener.
		 *
		 * @param previousObserver the previous observer
		 */
		public UpdateLayerListener(Observer previousObserver) 
		{
			super();
			this.previousObserver = previousObserver;
		}
		

		@Override
		public void actionPerformed(ActionEvent arg0) 
		{
			//get the photo details (archive worker handles only one photo at a time)
			SearchPhotoModel thePhotoWhoOwnsTheLayers = viewPhotoModel.getPhotosDetails().getPhotoHeadlineList().get(0);
			//create the gui , model and controller
			UpdatePhotoLayersGUI updatePhotoLayersView = new UpdatePhotoLayersGUI();
			UpdatePhotoLayersModel updatePhotoLayersModel = new UpdatePhotoLayersModel(thePhotoWhoOwnsTheLayers);
			UpdatePhotoLayersController updatePhotoLayersController =
					new UpdatePhotoLayersController(updatePhotoLayersView, updatePhotoLayersModel);
			
			//set current observer and back listener
			setCurrentObserver(updatePhotoLayersController);
			setBackListener(previousObserver, updatePhotoLayersView);
		}

	}
	
	
	/**
	 * This method prints a message that the delete photo process completed successfully.
	 */
	@Override
	public void handleDBRequestSuccess() {
		if(deleteFlag){
			viewPhotoView.showMessage("Photo Deleted Successfully!");
			deleteFlag = false;
			viewPhotoView.clickBackButton();
			return;
		}
		if(foundExpired && checkExperationDate){
			System.out.println("CHECK");
			String sql = "aduUPDATE sgi.customer_account "
								+"SET sgi.customer_account.Active = 0 "
								+"WHERE sgi.customer_account.Active = ANY (  SELECT subTable.Active "
											+"FROM (  SELECT cus1.accountID,cus1.Active,g.gid,g.gname "
													+"FROM sgi.customer_account AS cus1, sgi.groups AS g "
													+"WHERE cus1.TimeStamp < '"+timestamp+"' "
													+"AND cus1.subscription_type = g.gid "
													+"AND cus1.Active = 1 ) AS subTable)";
			checkExperationDate = false;
			searchDB(sql);
		}
		if(foundExpired && !checkExperationDate){
			checkExperationDate = false;
			foundExpired = false;
		}
	}

	@Override
	public void handleNullSearchResult() {
		if(permission.userHasGroup("Archive Worker")){
			return;
		}
		if(checkExperationDate){
			viewPhotoView.addLayerCustomerButtonToWindow();
			checkExperationDate = false;
			return;
		}
		
	}

	@Override
	public void handleDBSearchResult(CliMessage dbResult) {
		if(checkExperationDate){
			srs = dbResult.getSrs();
			System.out.println("The expired subscription: ");
			String msg = "The following subscriptions expired:\n";
			for (int i = 0; i < srs.size(); i++) {
				if(permission.userHasGroup(srs.get(i).get("GNAME")))
					msg += srs.get(i).get("GNAME") + "\n";
			}
			viewPhotoView.showMessage(msg);
			foundExpired = true;
			String sql = "aduSET SQL_SAFE_UPDATES=0";
			searchDB(sql);
			}
	}

	@Override
	public boolean validateInput() {
		// TODO Auto-generated method stub
		return false;
	}
	
	/**
	 * This message prints message saying the photo has been deleted.
	 */
	@Override
	public void handleDBRequestFail() {
		viewPhotoView.showMessage("Something went wrong, can't connect to Server!");
		viewPhotoView.clickBackButton();
	}

	/**
	 * This method fits image's size to JFrame's size.
	 * @param img BufferedImage which was sent from the server
	 * @return resized image which is fit the JFrame panel.
	 */
	private BufferedImage resizedImage(BufferedImage img){
		BufferedImage newImage = new BufferedImage(viewPhotoView.getPanelWidth(), viewPhotoView.getPanelHeight(), BufferedImage.TYPE_INT_RGB);
		Graphics g = newImage.createGraphics();
		g.drawImage(img, 0, 0, viewPhotoView.getPanelWidth(), viewPhotoView.getPanelHeight(), null);
		g.dispose();
		return newImage;
	}
	
}
